package edu.rice.ngc1.util.annotation.processor;

import java.io.BufferedWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.FilerException;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;

import edu.rice.ngc1.util.annotation.Tuple;

/**
 * <p>
 * Processes elements marked with the {@link Tuple} annotation.
 * </p>
 * 
 * <p>
 * All generated tuples will be placed in the package
 * {@code edu.rice.ngc1.util.tuple}. The name of each tuple class will
 * be {@code TupleX}, where X is the tuple's arity. Each tuple will
 * have as many type parameters as its arity, and will have fields
 * {@code v1, v2 ... vX}. Each tuple will have a constructor that
 * takes as many arguments as its arity.
 * </p>
 */
@SupportedAnnotationTypes({"edu.rice.ngc1.util.annotation.Tuple"})
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public final class TupleProcessor extends AbstractProcessor {

    private static final String PACKAGE = "edu.rice.ngc1.util.tuple";
    private static final DateFormat FORMAT1 =
            new SimpleDateFormat("yyyy-MM-dd");

    private static final DateFormat FORMAT2 = new SimpleDateFormat(
            "HH:mm:ss.SSSZZZZZ");

    private static String getNow() {
        Date now = new Date();
        return FORMAT1.format(now) + "T" + FORMAT2.format(now);
    }

    private static void writeLine(BufferedWriter writer, String str)
            throws IOException {
        writer.write(str);
        writer.newLine();
    }

    public static void generateTuple(int arity, Filer filer) throws IOException {
        String name = "Tuple" + arity;
        String[] types = new String[arity];
        String[] fields = new String[arity];
        String[] constructorParams = new String[arity];

        for (int i = 0; i < arity; ++i) {
            types[i] = "T" + (i + 1);
            fields[i] = "v" + (i + 1);
            constructorParams[i] = "p" + (i + 1);
        }

        BufferedWriter writer =
                new BufferedWriter(filer.createSourceFile(PACKAGE + "." + name)
                        .openWriter());

        // PACKAGE
        writeLine(writer, "package " + PACKAGE + ";");
        writer.newLine();

        // IMPORTS
        writeLine(writer, "import javax.annotation.Generated;");
        writer.newLine();

        // "@Generated"
        writeLine(writer,
                "@Generated(value=\"" + TupleProcessor.class.getCanonicalName()
                        + "\", " + "date=\"" + getNow() + "\")");

        // CLASS NAME
        writer.write("public final class " + name + "<");
        for (int i = 0; i < arity; ++i) {
            writer.write(types[i]);
            if (i < (arity - 1)) {
                writer.write(", ");
            }
        }
        writeLine(writer, "> {");

        // FIELDS
        for (int i = 0; i < arity; ++i) {
            writeLine(writer, "  public " + types[i] + " " + fields[i] + ";");
        }
        writer.newLine();

        // CONSTRUCTOR
        writer.write("  public " + name + "(");
        for (int i = 0; i < arity; ++i) {
            writer.write(types[i] + " " + constructorParams[i]);
            if (i < (arity - 1)) {
                writer.write(", ");
            }
        }
        writeLine(writer, ") {");
        for (int i = 0; i < arity; ++i) {
            writeLine(writer, "    " + fields[i] + " = " + constructorParams[i]
                    + ";");
        }
        writeLine(writer, "  }");

        writeLine(writer, "}");

        writer.close();
    }

    private void processElement(Element e) {
        Filer f = processingEnv.getFiler();
        Messager messager = processingEnv.getMessager();

        Tuple tup = e.getAnnotation(Tuple.class);
        String name = "Tuple" + tup.value();

        for (int i : tup.value()) {
            try {
                generateTuple(i, f);
            } catch (FilerException exc) {
                // tuple has already been generated
            } catch (IOException exc) {
                messager.printMessage(Kind.WARNING,
                        "IOException occured while generating " + name);
                exc.printStackTrace();
            }
        }
    }

    @Override
    public boolean process(Set<? extends TypeElement> arg0,
            RoundEnvironment arg1) {
        for (TypeElement annotation : arg0) {
            if (!annotation.getQualifiedName().contentEquals(
                    Tuple.class.getCanonicalName())) {
                throw new UnsupportedOperationException();
            }
            for (Element e : arg1.getElementsAnnotatedWith(annotation)) {
                processElement(e);
            }
        }

        return true;
    }
}
